package com.star.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.star.mentor.dto.MentorUpdateWxmentorDTO;
import com.star.security.utils.PasswordEncoders;
import com.star.system.config.LocalAuthenticationService;
import com.star.system.dto.*;
import com.star.system.entity.User;
import com.star.system.enums.UserType;
import com.star.system.mapper.UserMapper;
import com.star.system.service.RoleService;
import com.star.system.service.UserRoleService;
import com.star.system.service.UserService;
import com.star.system.service.WxappApi;
import com.star.system.struct.UserStruct;
import com.star.system.vo.LoginInfoVO;
import com.star.system.vo.UserPageVO;
import com.star.system.vo.UserRoleVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 用户 服务实现类
 *
 * @author yangzj
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private RoleService roleService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private WxappApi wxappApi;
    @Autowired
    private LocalAuthenticationService localAuthenticationService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void save(UserSaveDTO dto) {
        this.check(dto.getUsername(), null);
        User user = UserStruct.INSTANCE.to(dto)
                .setType(UserType.WEB.getCode())
                .setPassword(PasswordEncoders.encode(dto.getPassword()))
                .setEnabled(true);
        this.save(user);
        if (CollUtil.isNotEmpty(dto.getRoleIds())) {
            userRoleService.saveBatch(user.getId(), dto.getRoleIds());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void update(UserUpdateDTO dto, boolean isUpdateRole) {
        Long id = dto.getId();
        User rawUser = this.check(dto.getUsername(), id);
        User user = UserStruct.INSTANCE.to(dto);
        Optional.ofNullable(dto.getPassword())
                .ifPresent(password -> user.setPassword(PasswordEncoders.encode(password)));
        this.update(user, Wrappers.<User>lambdaUpdate()
                .eq(User::getId, dto.getId())
                .set(User::getNickName, dto.getNickName())
                .set(User::getAvatarUrl, dto.getAvatarUrl())
                .set(User::getPhone, dto.getPhone())
                .set(User::getProvince, dto.getProvince())
                .set(User::getCity, dto.getCity()));
        if (isUpdateRole) {
            userRoleService.updateBatch(id, dto.getRoleIds());
        }
        // 更新用户名需要重新登陆
        if (dto.getUsername().equals(rawUser.getUsername())) {
            localAuthenticationService.logout(rawUser);
        } else {
            localAuthenticationService.refresh(user);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateEnabled(Long id, boolean enabled) {
        User user = this.getUser(id);
        this.updateById(new User().setId(id).setEnabled(enabled));
        if (user.getEnabled().equals(enabled) || enabled) {
            return;
        }
        // 登出
        localAuthenticationService.logout(user);
    }

    /**
     * 检查
     *
     * @param id       id
     * @param username 用户名
     * @return {@link User} 更新时返回
     */
    private User check(String username, Long id) {
        boolean save = id == null;
        User user = save ? null : this.getUser(id);
        if (save || !user.getUsername().equals(username)) {
            Assert.isTrue(this.lambdaQuery().eq(User::getUsername, username).count() == 0
                    , "用户名称已存在");
        }
        return user;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updatePassword(UpdatePasswordDTO dto) {
        User user = this.getUser(dto.getUserId());
        Assert.isTrue(PasswordEncoders.matches(dto.getRawPassword(), user.getPassword()), "原密码不正确");
        this.updateById(new User().setId(user.getId()).setPassword(PasswordEncoders.encode(dto.getNewPassword())));
        // 重新登录
        localAuthenticationService.logout(user);
    }

    @Override
    public void setFirstPassword(SetFirstPasswordDTO dto) {
        User user = this.getUser(dto.getUserId());
        Assert.isTrue(user.getPassword() == null, "您已设置过密码，请前往修改密码");
        this.updateById(new User().setId(user.getId()).setPassword(PasswordEncoders.encode(dto.getPassword())));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteByIds(List<Long> ids) {
        List<User> users = this.listByIds(ids);
        this.removeByIds(ids);
        userRoleService.deleteByUserIds(ids);
        // 登出
        users.forEach(localAuthenticationService::logout);
    }

    @Override
    public IPage<UserPageVO> page(UserPageDTO dto) {
        IPage<UserPageVO> list = baseMapper.page(dto);
        List<UserPageVO> records = list.getRecords();
        if (records.isEmpty()) {
            return list;
        }
        // 设置用户拥有的角色列表
        List<Long> userIds = records.stream().map(UserPageVO::getId).collect(Collectors.toList());
        Map<Long, List<UserRoleVO>> roleMap = roleService.listUserRole(userIds)
                .stream().collect(Collectors.groupingBy(UserRoleVO::getUserId));
        records.forEach(c -> c.setRoles(roleMap.get(c.getId())));
        return list;
    }

    @Override
    public void updateWxappStyles(String styles, Long id) {
        this.update(Wrappers.<User>lambdaUpdate().eq(User::getId, id).set(User::getStyles, styles));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateWxappUser(UserUpdateWxappDTO dto) {
        User oldUser = this.getUser(dto.getId());
        User user = UserStruct.INSTANCE.to(dto);
        this.update(user, Wrappers.<User>lambdaUpdate()
                .eq(User::getId, dto.getId())
                .set(User::getNickName, dto.getNickName())
                .set(User::getAvatarUrl, dto.getAvatarUrl())
                .set(User::getPhone, dto.getPhone())
                .set(User::getProvince, dto.getProvince())
                .set(User::getCity, dto.getCity()));
        // 更新用户token
        localAuthenticationService.refresh(oldUser);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateWxappPhone(Long userId, String code, Integer type) {
        User user = this.getUser(userId);
        String phone = wxappApi.getPhone(code, type);
        this.updateById(new User().setId(userId).setPhone(phone));
        // 更新用户token
        localAuthenticationService.refresh(user);
    }

    @Override
    public void updateMentorInfo(MentorUpdateWxmentorDTO dto) {
        this.updateById(new User().setId(dto.getId())
                .setNickName(dto.getNickName()).setAvatarUrl(dto.getAvatarUrl())
                .setGender(dto.getGender()).setPhone(dto.getPhone()));
    }

    @Override
    public LoginInfoVO getLoginInfo(Long id) {
        return baseMapper.getLoginInfo(id);
    }

    @Override
    public String getTips(Long id) {
        return this.getById(id).getTips();
    }

    @Override
    public void updateTips(Long id, String tips) {
        this.updateById(new User().setId(id).setTips(tips));
    }

    /**
     * 获取使用者
     *
     * @param id 身份证件
     * @return {@link User}
     */
    private User getUser(Long id) {
        return Assert.notNull(this.getById(id), "用户不存在");
    }
}
